using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class TorusKnotGeometry : BufferGeometry
    {

        public class Parameters
        {
            public double radius;
            public double tube;
            public int tubularSegments;
            public int radialSegments;
            public double p;
            public double q;
        }
        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public TorusKnotGeometry(double radius = 1, double tube = 0.4, int tubularSegments = 64, int radialSegments = 8, double p = 2, double q = 3)
        {
            this.type = "TorusKnotGeometry";
            this.parameters = new Parameters()
            {
                radius = radius,
                tube = tube,
                tubularSegments = tubularSegments,
                radialSegments = radialSegments,
                p = p,
                q = q
            };
            //tubularSegments = (int)Math.Floor(tubularSegments);
            //radialSegments = (int)Math.Floor(radialSegments);
            // buffers
            var indices = new ListEx<int>();
            var vertices = new ListEx<double>();
            var normals = new ListEx<double>();
            var uvs = new ListEx<double>();
            // helper variables
            var vertex = new Vector3();
            var normal = new Vector3();
            var P1 = new Vector3();
            var P2 = new Vector3();
            var B = new Vector3();
            var T = new Vector3();
            var N = new Vector3();
            // generate vertices, normals and uvs
            for (int i = 0; i <= tubularSegments; ++i)
            {
                // the radian "u" is used to calculate the position on the torus curve of the current tubular segment
                var u = i / (double)tubularSegments * p * MathEx.PI * 2;
                // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead.
                // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions
                calculatePositionOnCurve(u, p, q, radius, P1);
                calculatePositionOnCurve(u + 0.01, p, q, radius, P2);
                // calculate orthonormal basis
                T.SubVectors(P2, P1);
                N.AddVectors(P2, P1);
                B.CrossVectors(T, N);
                N.CrossVectors(B, T);
                // Normalize B, N. T can be ignored, we don"t use it
                B.Normalize();
                N.Normalize();
                for (int j = 0; j <= radialSegments; ++j)
                {
                    // now calculate the vertices. they are nothing more than an extrusion of the torus curve.
                    // because we extrude a shape in the xy-plane, there is no need to calculate a z-value.
                    var v = j / (double)radialSegments * MathEx.PI * 2;
                    var cx = -tube * Math.Cos(v);
                    var cy = tube * Math.Sin(v);
                    // now calculate the final vertex position.
                    // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve
                    vertex.X = P1.X + (cx * N.X + cy * B.X);
                    vertex.Y = P1.Y + (cx * N.Y + cy * B.Y);
                    vertex.Z = P1.Z + (cx * N.Z + cy * B.Z);
                    vertices.Push(vertex.X, vertex.Y, vertex.Z);
                    // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal)
                    normal.SubVectors(vertex, P1).Normalize();
                    normals.Push(normal.X, normal.Y, normal.Z);
                    // uv
                    uvs.Push(i / (double)tubularSegments);
                    uvs.Push(j / (double)radialSegments);
                }
            }
            // generate indices
            for (int j = 1; j <= tubularSegments; j++)
            {
                for (int i = 1; i <= radialSegments; i++)
                {
                    // indices
                    var a = (radialSegments + 1) * (j - 1) + (i - 1);
                    var b = (radialSegments + 1) * j + (i - 1);
                    var c = (radialSegments + 1) * j + i;
                    var d = (radialSegments + 1) * (j - 1) + i;
                    // faces
                    indices.Push(a, b, d);
                    indices.Push(b, c, d);
                }
            }
            // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
            // this function calculates the current position on the torus curve
            void calculatePositionOnCurve(double u, double _p, double _q, double _radius, Vector3 position)
            {
                var cu = Math.Cos(u);
                var su = Math.Sin(u);
                var quOverP = _q / _p * u;
                var cs = Math.Cos(quOverP);
                position.X = _radius * (2 + cs) * 0.5 * cu;
                position.Y = _radius * (2 + cs) * su * 0.5;
                position.Z = _radius * Math.Sin(quOverP) * 0.5;
            }
        }
        #endregion

        #region methods
        public TorusKnotGeometry copy(TorusKnotGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                radius = source.parameters.radius,
                tube = source.parameters.tube,
                radialSegments = source.parameters.radialSegments,
                tubularSegments = source.parameters.tubularSegments,
                p = source.parameters.p,
                q = source.parameters.q,
            };
            return this;
        }
        #endregion
    }
}
